package tiny_file

import (
	"encoding/binary"
	"errors"
	"fmt"
	"github.com/zeebo/errs"
	"io"
	"os"
	"sync"
	"time"
)

var ErrTinyBlock = errs.Class("tinyBlock")

type tinyBlock struct {
	space   uint32
	size    uint32
	expire  uint32
	pieceId [16]byte
	sum     [16]byte
}

const tinyBlockSize = 44

func (t *tinyBlock) ToBytes() []byte {
	b := make([]byte, 0)
	b = binary.LittleEndian.AppendUint32(b, t.space)
	b = binary.LittleEndian.AppendUint32(b, t.size)
	b = binary.LittleEndian.AppendUint32(b, t.expire)
	b = append(b, t.pieceId[:]...)
	b = append(b, t.sum[:]...)
	return b
}
func NewTinyFromBytes(b []byte) (idx *tinyBlock, err error) {
	if len(b) != tinyBlockSize {
		return nil, ErrTinyBlock.New("errorLen")
	}
	idx = &tinyBlock{}
	idx.space = binary.LittleEndian.Uint32(b[0:])
	idx.size = binary.LittleEndian.Uint32(b[4:])
	idx.expire = binary.LittleEndian.Uint32(b[8:])
	copy(idx.pieceId[:], b[12:28])
	copy(idx.sum[:], b[28:44])
	return idx, nil
}

type largeFile struct {
	out       *os.File
	writeLock sync.Mutex
}

func (f *largeFile) close() {
	if f.out != nil {
		f.out.Close()
	}
}
func (f *largeFile) write(reader io.Reader, block tinyBlock, startPos int64) error {
	//写操作是不能并行。因为startPos也只有一个, 串行写入效率更高
	f.writeLock.Lock()
	defer f.writeLock.Unlock()
	_, err := f.out.Seek(startPos, io.SeekStart)
	if err != nil {
		return err
	}
	_, err = f.out.Write(block.ToBytes())
	if err != nil {
		return err
	}
	_, err = io.CopyN(f.out, reader, int64(block.size))
	return err
}
func (f *largeFile) read(reader *os.File, startPos int64) (*tinyBlock, []byte, error) {
	_, err := reader.Seek(startPos, io.SeekStart)
	if err != nil {
		return nil, nil, err
	}
	b, err := readTiny(reader)
	if err != nil {
		return nil, nil, err
	}
	block, err := NewTinyFromBytes(b)
	if err != nil {
		return nil, nil, err
	}
	data := make([]byte, block.size)
	_, err = reader.Read(data)
	if err != nil {
		return nil, nil, err
	}
	return block, data, nil
}
func readTiny(file io.Reader) ([]byte, error) {
	b := make([]byte, tinyBlockSize)
	_, err := file.Read(b)
	return b, err
}
func (f *largeFile) through(fileName string, cb func(pos int64, reader io.Reader, tiny *tinyBlock) error) error {
	file, err := os.OpenFile(fileName, os.O_RDONLY, os.ModePerm)
	if err != nil {
		fmt.Println(err)
		return err
	}
	defer file.Close()
	startPos := int64(0)
	for {
		_, err = file.Seek(startPos, io.SeekStart)
		if err != nil {
			return err
		}
		b, err := readTiny(file)
		if err != nil {
			if errors.Is(err, io.EOF) {
				err = nil
			}
			return err
		}
		block, err := NewTinyFromBytes(b)
		if err != nil {
			return err
		}

		if cb != nil {
			if errCB := cb(startPos, file, block); errCB != nil {
				return errCB
			}
		}
		startPos += int64(block.space)
		time.Sleep(time.Millisecond)
	}
}
